#include "Gui.hpp"

namespace red {
	namespace ui {
		const char LuaGui::className[] = "Gui";
		Lunar<LuaGui>::RegType LuaGui::methods[] = {
			LunarMethod(LuaGui, AddWidget),
			{ 0 }
		};
  

		void Gui::Initialize(sf::RenderWindow &pRenderWindow){
			mRenderWindow = &pRenderWindow;

			// Sets the Gui View (Window size) used for Gui Widget drawing
			mView = sf::View(RedToSFMLVector2F(Vector2I(mRenderWindow->GetWidth() / 2, mRenderWindow->GetHeight() / 2)), 
							 RedToSFMLVector2F(Vector2I(mRenderWindow->GetWidth(),	   mRenderWindow->GetHeight())));

			// Sets the Gui Size (Window size)
			SetSize(Vector2F((f32)mRenderWindow->GetWidth(), (f32)mRenderWindow->GetHeight()));

			// Transparent (cause its not a visible widget, just a container)
			SetColor(sf::Color(0,0,0,0));

			// Important pointers initialisation
			mHoveredWidget = this;
			mLastHoveredWidget = this;
			mFocusedWidget = 0;
			mLastFocusedWidget = 0;
		}

		void Gui::Display(){
			// Get the previous view
			const sf::View& oldView = mRenderWindow->GetView();

			// Draw on our own view
			mRenderWindow->SetView(mView); 
			mRenderWindow->Draw(*this);

			// Replace the previous view
			mRenderWindow->SetView(oldView);
		}

		void Gui::SendEvent(const sf::Event &pEvent){
			// boolean : true if the event must be send to the focusedWidget
			static bool updateFocusedWidget;
			updateFocusedWidget = false;



			switch(pEvent.Type){
			// RESIZED Event : Resize the whole GUI
			case sf::Event::Resized:
				SetSize(Vector2F((f32)pEvent.Size.Width, (f32)pEvent.Size.Height));
				break;


			// MOUSEPRESSED Event : Set the focused element and send it the event
			case sf::Event::MouseButtonPressed:
				FindFocusedWidget();

				// If the focused object has changed, we change it
				if(mLastFocusedWidget != mFocusedWidget){
					mLastFocusedWidget = mFocusedWidget;
					OnUpdate(Focus);
				}

				updateFocusedWidget = true;
				break;

   
			// MOUSEMOVED Event : Set the hovered element
			case sf::Event::MouseMoved:
				mHoveredWidget = this;
				FindHoveredWidget(this, Vector2F((f32)pEvent.MouseMove.X, (f32)pEvent.MouseMove.Y));

				// If the hovered widget has changed, we change it
				if(mLastHoveredWidget != mHoveredWidget)
					mLastHoveredWidget = mHoveredWidget;

				updateFocusedWidget = true;
				break;


			// MOUSERELEASED Event : update the focusedWidget
			case sf::Event::MouseButtonReleased:
				updateFocusedWidget = true;
				break;


			// Everything else : UNWANTED
			default:
				break;
			}
 
			// If the focused Widget need update and exist, send him the event
			if(updateFocusedWidget && mFocusedWidget)
				DistributeEvent(mFocusedWidget, pEvent);
		}

		bool Gui::IsListener(Widget* pWidget){
			return dynamic_cast<Listener*>(pWidget) ? true : false;
		}

		void Gui::DistributeEvent(Widget* pWidget, const sf::Event &pEvent){
			// Cast to retrieve possible Listener
			Listener* widgetListener = dynamic_cast<Listener*>(pWidget);

			// If it's indeed a listener, send him the event, and send it also to its children (MAYBE NOT)
			if(widgetListener){
				widgetListener->OnEvent(pEvent);
				/*
				for(WidgetList::iterator it = pWidget->mChildren.begin(); it != pWidget->mChildren.end(); ++it){
					widgetListener = dynamic_cast<Listener*>(*it);
					if(widgetListener)
						widgetListener->OnEvent(pEvent);
				}
				*/
			}
		}

		void Gui::FindHoveredWidget(Widget* pRoot, const Vector2F &pMouse){
			WidgetList& widgetList = pRoot->mChildren;

			// Cycle through this widget children, starting by the end (top of the Z-Buffer)
			for(WidgetList::reverse_iterator it = widgetList.rbegin(); it != widgetList.rend(); ++it){
				Widget* current = *it;
				// If a child is visible and focusable
				if(current->IsVisible() && current->IsFocusable()){
					// If there is a intersection between the mouse and the child
					if(current->GetRectangle().Intersects(Vector2F(pMouse)) == IR_In){
						// This child is the current hovered object and we continue to cycle through its children
						// for a more precise intersection
						mHoveredWidget = current;
						FindHoveredWidget(current, pMouse);
						return;
					}
				}
			}
		}

		void Gui::FindFocusedWidget(){
			if(mHoveredWidget && mHoveredWidget->IsFocusable())
				mFocusedWidget = mHoveredWidget;
		}

		void Gui::AddListener(Listener* pListener){
			switch(pListener->GetType()){
			case LT_Mouse:
				mMouseListeners.push_back(dynamic_cast<MouseListener*>(pListener));
				break;
			case LT_Key:
				mKeyListeners.push_back(dynamic_cast<KeyListener*>(pListener));
				break;
			}
		}

		void Gui::RemoveListener(Listener* pListener){
			switch(pListener->GetType()){
			case LT_Mouse:
				{
					MouseListener* ml = dynamic_cast<MouseListener*>(pListener);
					for(MouseListenerList::iterator it = mMouseListeners.begin(); it != mMouseListeners.end(); ++it)
						if((*it) == ml)
							mMouseListeners.erase(it);
				}
				break;
			case LT_Key:
				{
					KeyListener* kl = dynamic_cast<KeyListener*>(pListener);
					for(KeyListenerList::iterator it = mKeyListeners.begin(); it != mKeyListeners.end(); ++it)
						if((*it) == kl)
							mKeyListeners.erase(it);
				}
				break;
			}
		}

		void Gui::OnUpdate( WidgetParameter pParam ){
			switch(pParam){
			case Size:
				mView.Reset(sf::FloatRect(0, 0, GetSize().x, GetSize().y));
				Widget::OnUpdate(Size);
				break;
			case Focus:
				mFocusedWidget->ChangeDepth(AboveAll);
				break;
			default:
				break;
			}
		}

		int LuaGui::AddWidget(lua_State* L){
			LuaValue val;

			try{
				// Simple check for table existence as a parameter of function
				if(lua_istable(L, -1)){
					// Get the Widget type for further operations
					lua_pushstring(L, "type");
					lua_gettable(L, -2);
					if(!lua_isstring(L, -1))
						throw LuaException("LuaGui::AddWidget", "Must specify a type for the widget.\n");
	
					val = LuaValue(lua_tostring(L, lua_gettop(L)));
					lua_pop(L, 1);
	
					std::string widgetType = val.ToString();
					if(widgetType == "label")
							AddGuiLabel(L);
				} else
					throw LuaException("LuaGui::AddWidget", "Incorrect parameter given from Lua script.\n");

			} catch(LuaException e){
				std::cout << e.what() << std::endl;
			}
			return 0;
		}

		void LuaGui::AddGuiLabel(lua_State* L){
			std::string labelText, alignment = "absolute";
			f32 posX = 0, posY = 0;
			Color fg = Color::Black, bg = Color(0,0,0,0);
			LuaValue val;

			// Get text (needed)
				lua_pushstring(L, "text");
				lua_gettable(L, -2);
				if(!lua_isstring(L, -1))
					throw LuaException("LuaGui::AddGuiLabel", "Must specify a label text.\n");

				val = LuaValue(lua_tostring(L, lua_gettop(L)));
				labelText = val.ToString();
				lua_pop(L, 1);

			// Get alignment (if one given)
				lua_pushstring(L, "alignment");
				lua_gettable(L, -2);
				if(lua_isstring(L, -1)){
					val = LuaValue(lua_tostring(L, lua_gettop(L)));
					alignment = val.ToString();
				}
				lua_pop(L, 1);

			// Get posX and Y (offset if alignment != absolute)
				lua_pushstring(L, "position");
				lua_gettable(L, -2);
				if(lua_istable(L, -1)){
					if(val.RetrieveTable(L)){
						posX = val[0].ToFloat();
						posY = val[1].ToFloat();
					} else
						throw LuaException("LuaGui::AddGuiLabel", "Given position is invalid.\n");	
				}		
				lua_pop(L, 1);

			// Get text color
				lua_pushstring(L, "textcolor");
				lua_gettable(L, -2);
				if(lua_istable(L, -1)){
					if(val.RetrieveTable(L)){
						fg.R(val[0].ToFloat());
						fg.G(val[1].ToFloat());
						fg.B(val[2].ToFloat());
						if(val[3].GetTypeName() != "nil")
							fg.A(val[3].ToFloat());
						else
							fg.A(1.f);
					} else
						throw LuaException("LuaGui::AddGuiLabel", "Given Text Color is invalid.\n");
				}
				lua_pop(L, 1);

			// Get background color
				lua_pushstring(L, "backgroundcolor");
				lua_gettable(L, -2);
				if(lua_istable(L, -1)){
					if(val.RetrieveTable(L)){
						bg.R(val[0].ToFloat());
						bg.G(val[1].ToFloat());
						bg.B(val[2].ToFloat());
						if(val[3].GetTypeName() != "nil")
							bg.A(val[3].ToFloat());
						else
							bg.A(1.f);
					} else
						throw LuaException("LuaGui::AddGuiLabel", "Given Text Color is invalid.\n");
				}
				lua_pop(L, 1);

				Label* l = new Label();
				// Set Alignment and Offset if not absolute
				if(!l->SetAlignmentFromString(alignment, Vector2F(posX, posY)))
					throw LuaException("LuaGui::AddGuiLabel", "Given Alignment is invalid.\n");
				// If absolute, set absolute position
				if(alignment == "absolute")
					l->SetPosition(Vector2F(posX, posY));

				l->SetText(labelText);
				l->SetTextColor(fg);
				l->SetBackgroundColor(bg);

				// Just push the dynamically created widget in our vector for future destruction
				mLuaWidgets.push_back(static_cast<Widget*>(l));
				mGui->Add(*l);
		}
	}
}
